﻿using System;
using CoreCms.Net.WeChat.Service.HttpClients;
using Microsoft.AspNetCore.Mvc;
using SKIT.FlurlHttpClient.Wechat.Api;
using SKIT.FlurlHttpClient.Wechat.Api.Events;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
using CoreCms.Net.Caching.AccressToken;
using CoreCms.Net.Configuration;
using CoreCms.Net.IServices;
using CoreCms.Net.Loging;
using CoreCms.Net.Model.Entities;
using CoreCms.Net.Utility.Helper;
using CoreCms.Net.WeChat.Service.Configuration;
using CoreCms.Net.WeChat.Service.Mediator;
using CoreCms.Net.WeChat.Service.Models;
using CoreCms.Net.WeChat.Service.Options;
using CoreCms.Net.WeChat.Service.Utilities;
using MediatR;
using Microsoft.Extensions.Options;
using Newtonsoft.Json;
using SKIT.FlurlHttpClient.Wechat.Api.Models;
using LogLevel = NLog.LogLevel;
using CoreCms.Net.Services;
using SqlSugar;

namespace CoreCms.Net.Web.Controllers.WeChat
{

    /// <summary>
    /// 微信公众号消息推送对接
    /// </summary>
    public class WeChatOffiaccountNotifyController : ControllerBase
    {
        private readonly IWeChatApiHttpClientFactory _weChatApiHttpClientFactory;
        private readonly WeChatOptions _weChatOptions;
        private readonly IMediator _mediator;

        private readonly ICoreCmsUserWeChatInfoServices _weChatUserInfoServices;


        /// <summary>
        /// 原始的加密请求（如果不加密则为null）
        /// </summary>
        public XDocument? EcryptRequestDocument { get; set; } = null;

        /// <summary>
        /// 是否使用加密
        /// </summary>
        public bool UsingEncryptMessage = false;

        /// <summary>
        /// 是否取消执行
        /// </summary>
        public bool CancelExecute = false;
        /// <summary>
        /// 是否使用兼容模式
        /// </summary>
        public bool UsingCompatibilityModelEncryptMessage = false;

        /// <summary>
        /// 构造函数
        /// </summary>
        public WeChatOffiaccountNotifyController(IWeChatApiHttpClientFactory weChatApiHttpClientFactory, IOptions<WeChatOptions> weChatOptions, IMediator mediator, ICoreCmsUserWeChatInfoServices weChatUserInfoServices)
        {
            _weChatApiHttpClientFactory = weChatApiHttpClientFactory;
            _mediator = mediator;
            _weChatUserInfoServices = weChatUserInfoServices;
            _weChatOptions = weChatOptions.Value;
        }

        /// <summary>
        /// GET请求用于处理微信公众号后台的URL验证
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        [ActionName("Index")]
        public ActionResult Get([FromQuery(Name = "timestamp")] string timestamp,
            [FromQuery(Name = "nonce")] string nonce,
            [FromQuery(Name = "signature")] string signature,
            [FromQuery(Name = "echostr")] string echoString)
        {
            // 验证服务器推送
            // 文档：https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html

            var getModel = new
            {
                timestamp,
                nonce,
                signature,
                echoString
            };


            //NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "GET接收到微信推送的数据", JsonConvert.SerializeObject(getModel));

            //var client = _weChatApiHttpClientFactory.CreateWeXinClient();
            //var valid = client.VerifyEventSignatureForEcho(callbackTimestamp: timestamp, callbackNonce: nonce, callbackSignature: signature);

            //return Content(!valid ? "fail" : echoString);

            if (!CheckSignature.Check(signature, timestamp, nonce, _weChatOptions.WeiXinToken))
            {
                NLogUtil.WriteFileLog(LogLevel.Error, LogType.WeChat, "GET接收到微信推送的数据（签名错误）", JsonConvert.SerializeObject(getModel));
                return Content("fail");
            }
            else
            {
                NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "GET接收到微信推送的数据（签名成功）", JsonConvert.SerializeObject(getModel));
                return Content(echoString);
            }

        }

        /// <summary>
        /// 接收服务器推送
        /// </summary>
        /// <returns></returns>
        [HttpPost]
        [ActionName("Index")]
        public async Task<IActionResult> Post(PostModel postModel)
        {
            // 接收服务器推送
            // 文档：https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html

            if (!CheckSignature.Check(postModel.Signature, postModel.Timestamp, postModel.Nonce, _weChatOptions.WeiXinToken))
            {
                NLogUtil.WriteFileLog(LogLevel.Error, LogType.WeChat, "Post接收服务器推送（签名错误）", JsonConvert.SerializeObject(postModel));
                return Content("fail");
            }
            else
            {
                NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "Post接收服务器推送（签名成功）", JsonConvert.SerializeObject(postModel));
            }

            postModel.Token = _weChatOptions.WeiXinToken;//根据自己后台的设置保持一致
            postModel.EncodingAESKey = _weChatOptions.WeiXinEncodingAesKey;//根据自己后台的设置保持一致
            postModel.AppId = _weChatOptions.WeiXinAppId;//根据自己后台的设置保持一致（必须提供）

            //获取流数据转xml流
            XDocument postDataDocument = XmlUtility.Convert(Request.GetRequestStream());

            var msgXml = string.Empty;
            var callbackXml = Init(postDataDocument, postModel, ref msgXml);

            //怕出现误判，所以将最优结果判断
            if (callbackXml != null && CancelExecute == false && !string.IsNullOrEmpty(msgXml))
            {
                /* 如果是 XML 格式的通知内容 */
                NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "接收服务器推送（XML格式的通知内容）", JsonConvert.SerializeObject(callbackXml));
                var callBack = await ExecuteProcess(callbackXml, msgXml);
                NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "接收服务器推送（XML通知微信服务器）", callBack.Data);
                return Content(callBack.Data);
            }
            else
            {
                NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "接收服务器推送（解密失败）", JsonConvert.SerializeObject(callbackXml));
                return Content("fail");
            }
        }



        #region 处理xml内容
        /// <summary>
        /// 对解密后的xml数据进行筛选并分发处理结果
        /// </summary>
        public async Task<WeChatApiCallBack> ExecuteProcess(XDocument sourceXml, string msgXml)
        {
            //被动回复消息
            //文件：https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html
            var requestType = sourceXml.Root?.Element("MsgType")?.Value;

            WeChatApiCallBack callBack = new WeChatApiCallBack();

            if (!string.IsNullOrEmpty(requestType))
            {
                var client = _weChatApiHttpClientFactory.CreateWeXinClient();

                switch (requestType)
                {
                    case RequestMsgType.Text:
                        {
                            var eventModel = client.DeserializeEventFromXml<TextMessageEvent>(msgXml);
                            var replyModel = new TextMessageReply()
                            {
                                ToUserName = eventModel.FromUserName,
                                FromUserName = eventModel.ToUserName,
                                CreateTimestamp = CommonHelper.GetTimeStampByTotalSeconds(),
                                Content = "您发送的消息是" + eventModel.Content
                            };
                            var replyXml = client.SerializeEventToXml(replyModel, false);
                            callBack.Data = replyXml;
                        }
                        break;
                    case RequestMsgType.Location:
                        {
                            var eventModel = client.DeserializeEventFromXml<LocationMessageEvent>(msgXml);

                        }
                        break;
                    case RequestMsgType.Image:
                        {
                            var eventModel = client.DeserializeEventFromXml<ImageMessageEvent>(msgXml);

                            var replyModel = new ImageMessageReply()
                            {
                                ToUserName = eventModel.FromUserName,
                                FromUserName = eventModel.ToUserName,
                                CreateTimestamp = CommonHelper.GetTimeStampByTotalSeconds(),

                            };
                            var replyXml = client.SerializeEventToXml(replyModel, false);
                            callBack.Data = replyXml;

                        }
                        break;
                    case RequestMsgType.Voice:
                        {
                            var eventModel = client.DeserializeEventFromXml<VoiceMessageEvent>(msgXml);

                            var replyModel = new VoiceMessageReply()
                            {
                                ToUserName = eventModel.FromUserName,
                                FromUserName = eventModel.ToUserName,
                                CreateTimestamp = CommonHelper.GetTimeStampByTotalSeconds(),

                            };
                            var replyXml = client.SerializeEventToXml(replyModel, false);
                            callBack.Data = replyXml;

                        }
                        break;
                    case RequestMsgType.Video:
                        {
                            var eventModel = client.DeserializeEventFromXml<VideoMessageEvent>(msgXml);

                            var replyModel = new VideoMessageReply()
                            {
                                ToUserName = eventModel.FromUserName,
                                FromUserName = eventModel.ToUserName,
                                CreateTimestamp = CommonHelper.GetTimeStampByTotalSeconds(),

                            };
                            var replyXml = client.SerializeEventToXml(replyModel, false);
                            callBack.Data = replyXml;
                        }
                        break;
                    case RequestMsgType.ShortVideo:
                        {
                            var eventModel = client.DeserializeEventFromXml<ShortVideoMessageEvent>(msgXml);


                        }
                        break;
                    case RequestMsgType.Link:
                        {
                            var eventModel = client.DeserializeEventFromXml<VoiceMessageEvent>(msgXml);

                        }
                        break;
                    case RequestMsgType.MessageEvent:
                        {
                            var eventType = sourceXml.Root?.Element("Event")?.Value;
                            if (!string.IsNullOrEmpty(eventType))
                            {
                                switch (eventType)
                                {
                                    //订阅（关注）事件
                                    case EventType.Subscribe:
                                        {
                                            var eventModel = client.DeserializeEventFromXml<SubscribePushEvent>(msgXml);
                                            var accessToken = WeChatCacheAccessTokenHelper.GetWeChatAccessToken();

                                            if (eventModel.FromUserName != null)
                                            {
                                                var request = new CgibinUserInfoRequest() { AccessToken = accessToken, OpenId = eventModel.FromUserName };
                                                var response = await client.ExecuteCgibinUserInfoAsync(request, cancellationToken: HttpContext.RequestAborted);
                                                if (!response.IsSuccessful())
                                                {
                                                    NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "获取用户基本信息失败", @"获取用户基本信息失败（状态码：{response.RawStatus}，错误代码：{response.ErrorCode}，错误描述：{ response.ErrorMessage}）。");
                                                }
                                                else
                                                {
                                                    var userInfo = await _weChatUserInfoServices.QueryByClauseAsync(p => p.openid == eventModel.FromUserName);
                                                    if (userInfo == null)
                                                    {

                                                        userInfo = new Model.Entities.CoreCmsUserWeChatInfo()
                                                        {
                                                            isSubscribe = response.IsSubscribed,
                                                            openid = response.OpenId,
                                                            language = response.Language,
                                                            //createTime = response.SubscribeTimestamp,
                                                            type = (int)GlobalEnumVars.UserAccountTypes.微信公众号,
                                                            //sessionKey = response.s,
                                                            gender = 1,
                                                            createTime = DateTime.Now,
                                                            unionId = response.UnionId,
                                                        };
                                                        var id = await _weChatUserInfoServices.InsertAsync(userInfo);
                                                        if (id > 0)
                                                        {
                                                            await _weChatUserInfoServices.UpdateAsync(
                                                                p => new Model.Entities.CoreCmsUserWeChatInfo()
                                                                {
                                                                    userId = id
                                                                }, p => p.id == id);
                                                        }
                                                    }
                                                    else
                                                    {
                                                        userInfo.isSubscribe = response.IsSubscribed;
                                                        userInfo.unionId = response.UnionId;
                                                        userInfo.updateTime = DateTime.Now;

                                                        await _weChatUserInfoServices.UpdateAsync(userInfo);
                                                    }
                                                }
                                            }
                                        }
                                        break;
                                    // 退订
                                    // 实际上用户无法收到非订阅账号的消息，所以这里可以随便写。
                                    // unsubscribe事件的意义在于及时删除网站应用中已经记录的OpenID绑定，消除冗余数据。并且关注用户流失的情况。
                                    case EventType.Unsubscribe:
                                        {


                                        }
                                        break;
                                    case EventType.Localtion:
                                        {


                                        }
                                        break;
                                    case EventType.Click:
                                        {
                                            var eventModel = client.DeserializeEventFromXml<ClickPushEvent>(msgXml);

                                            var replyModel = new TextMessageReply()
                                            {
                                                ToUserName = eventModel.FromUserName,
                                                FromUserName = eventModel.ToUserName,
                                                CreateTimestamp = CommonHelper.GetTimeStampByTotalSeconds(),
                                                Content = "您刚才发送了ENTER事件请求"
                                            };
                                            var replyXml = client.SerializeEventToXml(replyModel, false);
                                            callBack.Data = replyXml;

                                        }
                                        break;
                                    default:
                                        NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "接收服务器推送（处理xml内容/Event无匹配）", JsonConvert.SerializeObject(sourceXml));
                                        break;
                                }
                            }
                        }
                        break;
                    default:
                        NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "接收服务器推送（处理xml内容/MsgType无匹配）", JsonConvert.SerializeObject(sourceXml));
                        break;
                }
            }
            else
            {
                NLogUtil.WriteFileLog(LogLevel.Info, LogType.WeChat, "接收服务器推送（处理xml内容/获取MsgType失败）", JsonConvert.SerializeObject(sourceXml));
            }

            return callBack;

        }
        #endregion

        #region 初始化获取xml文本数据

        /// <summary>
        /// 初始化获取xml文本数据
        /// </summary>
        /// <param name="postDataDocument"></param>
        /// <param name="postModel"></param>
        /// <param name="msgXml"></param>
        /// <returns></returns>
        private XDocument? Init(XDocument postDataDocument, PostModel postModel, ref string msgXml)
        {
            //进行加密判断并处理
            var postDataStr = postDataDocument.ToString();
            XDocument decryptDoc = postDataDocument;
            if (postDataDocument.Root?.Element("Encrypt") != null && !string.IsNullOrEmpty(postDataDocument.Root.Element("Encrypt")?.Value))
            {
                //使用了加密
                UsingEncryptMessage = true;
                EcryptRequestDocument = postDataDocument;

                WXBizMsgCrypt msgCrype = new WXBizMsgCrypt(postModel.Token, postModel.EncodingAESKey, postModel.AppId);

                var result = msgCrype.DecryptMsg(postModel.Msg_Signature, postModel.Timestamp, postModel.Nonce, postDataStr, ref msgXml);
                //判断result类型
                if (result != 0)
                {
                    //验证没有通过，取消执行
                    CancelExecute = true;
                    return null;
                }
                if (postDataDocument.Root.Element("FromUserName") != null && !string.IsNullOrEmpty(postDataDocument.Root.Element("FromUserName")?.Value))
                {
                    //TODO：使用了兼容模式，进行验证即可
                    UsingCompatibilityModelEncryptMessage = true;
                }
                decryptDoc = XDocument.Parse(msgXml);//完成解密
            }
            return decryptDoc;

        }


        #endregion

    }
}
